/* Copyright (c) 2009, 2010, 2011 Damian Kmiecik <d0zoenator@gmail.com>
   All rights reserved.

   Redistribution and use in source and binary forms, with or without
   modification, are permitted provided that the following conditions are met:

   * Redistributions of source code must retain the above copyright
     notice, this list of conditions and the following disclaimer.
   * Redistributions in binary form must reproduce the above copyright
     notice, this list of conditions and the following disclaimer in
     the documentation and/or other materials provided with the
     distribution.
   * Neither the name of the copyright holders nor the names of
     contributors may be used to endorse or promote products derived
     from this software without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
  ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
  LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
  CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
  SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
  INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  POSSIBILITY OF SUCH DAMAGE. */

#include <stdlib.h>
#include <avr/io.h>
#include <inttypes.h>
#include <util/delay.h>
#include <avr/interrupt.h>
#include <avr/pgmspace.h>
#include <string.h>
#include <avr/wdt.h>
#include <inttypes.h>
#include <util/delay.h>
#include <stdio.h>
#include "config.h"
#include "main.h"
#include "enc28j60.h"
#include "stdout.h"
#include "checksum.h"
#include "timer.h"
#include "ethernet.h"

#ifndef	ARP_DEBUG
	#define ARP_DEBUG(...)
#endif

struct ARP_TABLE arpTable[MAX_ARP_ENTRY];
struct ETH_ARP_HEADER *arp = (struct ETH_ARP_HEADER*)&ethBuffer[ARP_OFFSET];
struct ETH_ICMP_HEADER *icmp = (struct ETH_ICMP_HEADER*)&ethBuffer[ICMP_OFFSET];
struct ETH_IP_HEADER  *ip = (struct ETH_IP_HEADER*)&ethBuffer[IP_OFFSET];

#ifdef DEBUG_ARP
void ARPprintTable(void)
{
	printf_P(PSTR("_ ARP TABLE _\n"));
	for (uint8_t i = 0; i < MAX_ARP_ENTRY; i++)
	{
		if (arpTable[i].arp_t_ip != 0)
			printf_P(PSTR("%d.%d.%d.%d is %X:%X:%X:%X:%X:%X for %d\n"),
					((uint8_t*)&(arpTable[i].arp_t_ip))[0], ((uint8_t*)&(arpTable[i].arp_t_ip))[1],
					((uint8_t*)&(arpTable[i].arp_t_ip))[2], ((uint8_t*)&(arpTable[i].arp_t_ip))[3],
					arpTable[i].arp_t_mac[0], arpTable[i].arp_t_mac[1],
					arpTable[i].arp_t_mac[2], arpTable[i].arp_t_mac[3],
					arpTable[i].arp_t_mac[4], arpTable[i].arp_t_mac[5],
					arpTable[i].arp_t_time);
	}
	printf_P(PSTR("_ ARP TABLE END _\n"));
}
#endif

void ARPtimerDo(void)
{
	// Refresh time
	for (uint8_t i = 0; i < MAX_ARP_ENTRY; i++)
	{
		// Check time
		if (arpTable[i].arp_t_ip != 0 && arpTable[i].arp_t_time == 0)
			// Free
			arpTable[i].arp_t_ip = 0;
		else if (arpTable[i].arp_t_ip != 0 && arpTable[i].arp_t_time != ARP_TIME_OFF)
			arpTable[i].arp_t_time--;
	}
}

void ARPentryAdd(void)
{
	// Check if MAC is in ARP table
	for (uint8_t i = 0; i < MAX_ARP_ENTRY; i++)
	{
		if (ethernet->type == HTONS(ETH_TYPE_IP))
		{
			if (arpTable[i].arp_t_ip == ip->sourceIp)
			{
				if (arpTable[i].arp_t_time != ARP_TIME_OFF)
					arpTable[i].arp_t_time = MAX_ARP_ENTRY_TIME;
				return;
			}
		}
		if (ethernet->type == HTONS(ETH_TYPE_ARP))
		{
			if (arpTable[i].arp_t_ip == arp->senderIp)
			{
				if (arpTable[i].arp_t_time != ARP_TIME_OFF)
					arpTable[i].arp_t_time = MAX_ARP_ENTRY_TIME;
				return;
			}
		}
	}
	// Find free entry and save
	for (uint8_t i = 0; i < MAX_ARP_ENTRY; i++)
	{
		if (arpTable[i].arp_t_ip == 0)
		{
			if (ethernet->type == HTONS(ETH_TYPE_IP))
			{
				for (uint8_t a = 0; a < 6; a++)
					arpTable[i].arp_t_mac[a] = ethernet->sourceMac[a];
				arpTable[i].arp_t_ip = ip->sourceIp;
				arpTable[i].arp_t_time = MAX_ARP_ENTRY_TIME;
				return;
			}
			if (ethernet->type == HTONS(ETH_TYPE_ARP))
			{
				for (uint8_t a = 0; a < 6; a++)
					arpTable[i].arp_t_mac[a] = arp->senderMac[a];
				arpTable[i].arp_t_ip = arp->senderIp;
				arpTable[i].arp_t_time = MAX_ARP_ENTRY_TIME;
				return;
			}
			ARP_DEBUG("Bad ARP or IP packet\n", NULL);
		}
	}
	ARP_DEBUG("ARP table full\n", NULL);
}

uint8_t ARPentrySearch(uint32_t ip)
{
	// Check routing
	if ((ip & settings.mask) != (ip & settings.mask))
	{
		// use router
		ip = settings.gate;
	}
	// Check if MAC is in ARP table
	for (uint8_t i = 0; i < MAX_ARP_ENTRY; i++)
	{
		if (arpTable[i].arp_t_ip == ip)
		{
			return (i);
		}
	}
	return (MAX_ARP_ENTRY);
}

void ARPreply(void)
{
	ARP_DEBUG("ARP ", NULL);
	// Check is ARP request OK
	if ((arp->hwType == HTONS(ETH_HW_ETHERNET)) && (arp->prType
			== HTONS(ETH_TYPE_IP)) && (arp->targetIp == settings.ip))
	{
		if (arp->opcode == ETH_ARP_REQUEST)
		{
			ARP_DEBUG("request\n", NULL);
			for (uint8_t i = 0; i < 6; i++)
			{
				// Set destination address
				ethernet->destMac[i] = ethernet->sourceMac[i];
				// Set source address
				ethernet->sourceMac[i] = settings.mac[i];
				// Write out data to the ARP packet structure
				arp->targetMac[i] = ethernet->destMac[i];
				arp->senderMac[i] = settings.mac[i];
			}
			// Set ARP opcode
			arp->opcode = ETH_ARP_REPLY;
			// Write out data to the ARP packet structure
			arp->targetIp = arp->senderIp;
			arp->senderIp = settings.ip;
			// Send packet
			enc28j60SendPacket(ARP_REPLY_LEN, ethBuffer);
		} else if (arp->opcode == ETH_ARP_REPLY) {
			ARP_DEBUG("reply\n", NULL);
			ARPentryAdd();
		}
	}
}

uint8_t ARPrequest(uint32_t destIp)
{
	ARP_DEBUG("Sending ARP request\n", NULL);
	uint32_t destIpStore = destIp;
	// Check routing
	if ((destIp & settings.mask) != (settings.ip & settings.mask))
	{
		// use router
		destIp = settings.gate;
	}
	// Make packet
	ethernet->type = HTONS(ETH_TYPE_ARP);
	ETHmakeHeader(destIp);
	arp->senderIp = settings.ip;
	arp->targetIp = destIp;
	for (uint8_t i = 0; i < 6; i++)
	{
		arp->senderMac[i] = settings.mac[i];
		arp->targetMac[i] = 0x00;
	}
	arp->hwType = HTONS(ETH_HW_ETHERNET);
	arp->prType = HTONS(ETH_TYPE_IP);
	arp->hwLen = 6;
	arp->prLen = 4;
	arp->opcode = ETH_ARP_REQUEST;
	// Send
	enc28j60SendPacket(ARP_REQUEST_LEN, ethBuffer);
	// Check reply
	for (uint8_t i = 0; i < 20; i++)
	{
		// Wait
		_delay_ms(10);
		// Refresh data
		ETHgetData();
		uint8_t index = ARPentrySearch(destIp);
		uint8_t index2 = ARPentrySearch(destIpStore);
		if ((index < MAX_ARP_ENTRY) || (index2 < MAX_ARP_ENTRY))
			return 1;
	}
	return 0;
}

void ICMPsend(const uint32_t destIp, const uint8_t type, const uint16_t seq,
		const uint16_t id)
{
	// Echo packet
#if ETHERNET_SENDING_PING
	icmp->type = type;
	eth.ping_ip = destIp;
	eth.ping = 0;
#else
	icmp->type = ICMP_REPLY;
#endif
	icmp->code = 0;
	icmp->id = id;
	icmp->seqNum = seq;
	icmp->checksum = 0;
	ip->totalLen = HTONS(0x0054);
	ip->protocol = IP_PROTOCOL_ICMP;
	IPmakeHeader(destIp);
	// Checksum compute
	uint16_t result16 = ((ip->totalLen & 0xFF00) >> 8) | ((ip->totalLen
			& 0x00FF) << 8);
	result16 = result16 - ((ip->version & 0x0F) << 2);
	result16 = checksum(&icmp->type, result16, 0);
	icmp->checksum = ((result16 & 0xFF00) >> 8) | ((result16 & 0x00FF) << 8);
	// send
	enc28j60SendPacket(ICMP_REPLY_LEN, ethBuffer);
	ARP_DEBUG("PING send to %d.%d.%d.%d\n",
		((uint8_t*) &(destIp))[0],
		((uint8_t*) &(destIp))[1],
		((uint8_t*) &(destIp))[2],
		((uint8_t*) &(destIp))[3]
	);
}

void IPmakeHeader(const uint32_t destIp)
{
	// Ethernet header
	ETHmakeHeader(destIp);
	ethernet->type = HTONS(ETH_TYPE_IP);
	// IP
	ip->offset = 0x0040;
	ip->ttl = 128;
	ip->version = 0x45;
	ip->headerLen = 0;
	ip->destIp = destIp;
	ip->sourceIp = settings.ip;
	ip->checksum = 0;
	//Compute checksum
	uint16_t result16 = (ip->version & 0x0F) << 2;
	result16 = checksum(&ip->version, result16, 0);
	ip->checksum = ((result16 & 0xFF00) >> 8) | ((result16 & 0x00FF) << 8);
}
